# _*_ coding : utf-8 _*_
# @Time : 2025/5/6 11:19
# @Author : liuchengjie
# @File : DHT11_Driver
# @Project : esp32
from machine import Pin
import time
import utime


class DHT11:
    """
    DHT11 temperature and humidity sensor driver for MicroPython.

    Based on ESP-IDF implementation using direct pin manipulation and precise timing.
    """

    def __init__(self, pin):
        """
        Initialize the DHT11 sensor.

        Args:
            pin: GPIO pin number the sensor is connected to
        """
        self.pin = pin
        self.pin_obj = Pin(pin, Pin.OUT)
        self.data = bytearray(5)  # DHT11 sends 5 bytes of data

    def measure(self):
        """
        Measure temperature and humidity from DHT11.

        Returns:
            tuple: (humidity, temperature * 10) if successful, or (None, None) if failed
        """
        # Reset data array
        self.data = bytearray(5)

        # Send start signal
        self._send_start_signal()

        # Check for sensor response and read data
        if not self._wait_for_response():
            return None, None

        # Read 40 bits of data (5 bytes)
        self._read_data()

        # Verify checksum
        if not self._verify_checksum():
            return None, None

        # Extract humidity and temperature
        humidity = self.data[0]
        temp_x10 = self.data[2] * 10 + self.data[3]

        # Validate readings
        if humidity > 100 or temp_x10 > 600:
            return None, None

        return humidity, temp_x10

    def _send_start_signal(self):
        """Send start signal to DHT11"""
        # Set pin as output
        self.pin_obj = Pin(self.pin, Pin.OUT)

        # Pull high for 1ms
        self.pin_obj.value(1)
        utime.sleep_us(1000)

        # Pull low for 20ms
        self.pin_obj.value(0)
        utime.sleep_us(20000)

        # Pull high for 20us
        self.pin_obj.value(1)
        utime.sleep_us(20)

        # Set pin as input with pull-up
        self.pin_obj = Pin(self.pin, Pin.IN, Pin.PULL_UP)

    def _wait_for_response(self):
        """
        Wait for DHT11 response.

        Returns:
            bool: True if response detected, False otherwise
        """
        # Wait for pin to go low (response start)
        count = 0
        while self.pin_obj.value():
            count += 1
            utime.sleep_us(1)
            if count > 100:  # Timeout after 100us
                return False

        # Wait for pin to go high (response middle)
        count = 0
        while not self.pin_obj.value():
            count += 1
            utime.sleep_us(1)
            if count > 100:  # Timeout after 100us
                return False

        # Wait for pin to go low (response end)
        count = 0
        while self.pin_obj.value():
            count += 1
            utime.sleep_us(1)
            if count > 100:  # Timeout after 100us
                return False

        return True

    def _read_data(self):
        """Read 40 bits of data from DHT11"""
        for i in range(40):
            # Wait for pin to go high (bit start)
            while not self.pin_obj.value():
                pass

            # Measure high pulse width
            start = utime.ticks_us()
            while self.pin_obj.value():
                if utime.ticks_diff(utime.ticks_us(), start) > 100:
                    break

            duration = utime.ticks_diff(utime.ticks_us(), start)

            # Determine bit value based on pulse width
            bit = 1 if duration > 35 else 0

            # Store bit in data
            byte_index = i // 8
            bit_position = 7 - (i % 8)
            self.data[byte_index] |= bit << bit_position

    def _verify_checksum(self):
        """
        Verify data checksum.

        Returns:
            bool: True if checksum is valid, False otherwise
        """
        checksum = (self.data[0] + self.data[1] + self.data[2] + self.data[3]) & 0xFF
        return checksum == self.data[4]


# Usage example
if __name__ == "__main__":
    # Initialize DHT11 sensor on GPIO pin 4
    dht = DHT11(4)

    while True:
        # Read temperature and humidity
        humidity, temp_x10 = dht.measure()

        if humidity is not None and temp_x10 is not None:
            print(f"Temperature: {temp_x10 / 10:.1f}°C, Humidity: {humidity}%")
        else:
            print("Failed to read data from DHT11")

        # Wait before next reading
        time.sleep(2)
